class HtmlConsole {
    container: HTMLDivElement;
    input: HTMLInputElement;
    text: HTMLSpanElement;
    settings: HtmlConsoleSettings = new HtmlConsoleSettings();
    readers: Array< (s: string) => boolean> = new Array<(s: string) => boolean>();
    lineCount: number = 0;
    get width(): number {
        var fontTest = document.createElement("span");
        var testStr = "qwertyuiopasdfghjklzxcvbnm,./;'[]"
        fontTest.innerText = testStr;
        fontTest.style.visibility = "hidden";
        this.container.appendChild(fontTest);
        return $(this.container).width() / ($(fontTest).width() / testStr.length);
    }

    constructor(container: HTMLDivElement) {
        this.container = container;
        this.text = <HTMLSpanElement>document.createElement("span");
        this.text.classList.add("console-text");
        container.appendChild(this.text);
        this.input = <HTMLInputElement>document.createElement("input");
        this.input.type = "text";
        this.input.classList.add("console-input");
        this.input.id = "console-input";
        container.appendChild(this.input);
        container.onkeydown = (ev) => {
            if (this.input !== document.activeElement) {
                this.input.focus();
                this.input.onkeydown(ev);
            }
        }
        this.input.onkeydown = (ev) => {
            this.KeyPress(ev);
        }
        this.EndLine();
    }

    public Focus(): void {
        this.input.focus();
    }

    public Write(text: string): void {
        var lines = text.split("\n");
        lines.forEach((line, i) => {
            if (i != 0) this.EndLine();
            this.WriteOnLine(line);
        });
    }

    public WriteLine(text: string) {
        this.Write(text);
        this.EndLine();
    }

    private WriteOnLine(text: string) {
        //entitize
        var text = text.replace(/[\u00A0-\u9999<>\&\s]/gim, function (i) {
            if (i == " ") return "&nbsp;";
            if (i == "\t") return "&nbsp;&nbsp;&nbsp;&nbsp;";
            return '&#' + i.charCodeAt(0) + ';';
        });
        //span for the text
        var span = document.createElement("span");
        this.settings.ApplyStyles(span);
        span.innerHTML = text;
        this.text.lastElementChild.appendChild(span);
        var offset = $(span).offset().top + $(span).outerHeight();
        var pos = $(window).scrollTop() + window.innerHeight - offset;
        if (pos < 0) window.scrollBy(0, -pos)
    }

    private EndLine(): void {
        var separator = document.createElement("div");
        separator.classList.add("separator");
        this.text.appendChild(separator);
        var line = document.createElement("span");
        line.classList.add("line");
        line.id = "line-" + this.lineCount;
        this.text.appendChild(line);
        this.lineCount++;
    }

    KeyPress(ev: KeyboardEvent, enter = true): void
    {
        if (ev.keyCode == 13 && enter) {
            var val = (<HTMLInputElement>ev.srcElement).value;
            this.input.value = "";
            this.WriteLine(val);
            this.CallReaders(val);
            this.Write(">");
        }
        this.settings.ApplyStyles(this.input);
        this.input.size = this.input.value.length + 3;
    }

    public ReadLine(callback: (s: string) => boolean): void {
        this.readers.push(callback);
    }

    public CallReaders(text: string): void {
        this.readers = this.readers.filter((f) => f(text));
    }

    public static Init() {
        var elements = document.getElementsByClassName("console");
        for (var i = 0; i < elements.length; i++) {
            if(elements.item(i) instanceof HTMLDivElement)
                HtmlConsole.Repository.push(new HtmlConsole(<HTMLDivElement>elements.item(i)));
        }
    }

    public static Repository: Array<HtmlConsole> = new Array<HtmlConsole>();
}

class HtmlConsoleSettings {
    foregroundColor: string;
    backgroundColor: string;

    public ApplyStyles(element: HTMLElement): void {
        element.style.color = this.foregroundColor;
        element.style.backgroundColor = this.backgroundColor;
    }
}